﻿#-*- coding: gbk -*-
# author: vx(wanjochan)
# date: 2022-05-14

if False:
  r"""
  
严正声明！本人没有对官方软件做任何破解、逆向工程、入侵或者盈利性修改，所有代码都是在官方软件环境下原生实现，且仅为示范参考，不提供任何有偿服务！


## 2022-05-02 兼容极简模式（但是官方威胁说可能随时下架，且用且珍惜 ;）
  > c:\dev\qmt_mini\bin.x64\XtMiniQmt.exe
  > c:\dev\qmt_mini\bin.x64\python368.exe svr_qmtipc.py c:\dev\qmt_mini\userdata_mini 0

# 无依赖暴露QMT交易接口 (ipc模式)
https://gitee.com/miguoliang/shared-data/blob/master/tools/svr_qmtipc.py

# 演示客户端调用
https://gitee.com/miguoliang/shared-data/blob/master/tools/clt_ipc.py

注意，

由于QMT是c++管理线程会有阻塞，所以如果选择暴露接口，那么这个QMT实例就专注暴露，不要跑策略

另外分享每天重启的bat脚本如下(路径自己改一下)，qmt要设置自动登陆和启动策略

taskkill /F /IM:XtItClient.exe 
move /y d:\qmt20220113\userdata\log d:\qmt_userdata_log
D:\qmt20220113\bin.x64\XtItClient.exe 

"""

def tryx(l,e=print):
    try: return l()
    except Exception as ex: return ex if True==e else e(ex) if e else None

# o2s/s2o json编码大法
import json
class MyJsonEncoder(json.JSONEncoder):
    def default(self, obj): return tryx(lambda:json.JSONEncoder.default(self,obj),str)
s2o = lambda s:tryx(lambda:json.loads(s))
o2s = lambda o,indent=None:tryx(lambda:json.dumps(o, indent=indent, ensure_ascii=False, cls=MyJsonEncoder))

read = lambda f,m='r',encoding='utf-8':open(f,m,encoding=encoding).read()
write = lambda f,s,m='w',encoding='utf-8':open(f,m,encoding=encoding).write(s)

import sys,os

#print("os.environ",os.environ)

argv = sys.argv
argc = len(argv)
print("argv",argv)
#print("pwd",os.path.abspath("."))

g_ctx = None # 全局 ContextInfo
g_acct = tryx(lambda:argv[3],False) or "66301009048" # 换成自己的账号

address = r'\\.\Pipe\qmtipc' # win-named-pipe mode
address = ('0.0.0.0', 16666) # or (ip:port) mode

server = None
server_i = 0

# 时间转换万能工具函数
def time_maker(days=0,date=None,outfmt=None,infmt='%Y-%m-%d',
        months=0):
    from datetime import datetime,timedelta
    from time import mktime
    if date is None: _dt = datetime.now()
    else: _dt = datetime.fromtimestamp(int(date))\
        if infmt=='0' or not infmt\
        else datetime.strptime(str(date),infmt)
    if months>0 or months<0:
        from dateutil.relativedelta import relativedelta
        _dt += relativedelta(months=months)
    _dt += timedelta(days=days)
    if outfmt is None: outfmt = infmt
    if outfmt=='0' or not outfmt:
        return int(mktime(_dt.timetuple()))
    return _dt.strftime(outfmt)

"""
e.g.
[(v.m_dOrderPriceRMB,v.m_nOrderPriceType) for v in get_trade_detail_data(g_acct,'stock','order')]

fields:
(lambda o:[(k,getattr(o,k)) for k in dir(o) if 'm_' in k])(get_trade_detail_data(g_acct,'stock','order')[0])
[["m_dOrderPriceRMB", 1.7976931348623157e+308],
["m_nOrderPriceType", 50],
["m_nOrderStatus", 56],
["m_nOrderStrategyType", 3145786],
["m_nOrderSubmitStatus", 51],
["m_strOrderParam", ""],
["m_strOrderRef", "1697620407772774430"],
["m_strOrderStrategyType", "函数下单"],
["m_strOrderSysID", "2971"]]

[('m_bEnable', True), ('m_dCancelAmount', 100.0), ('m_dFrozenCommission', 0.0), ('m_dFrozenMargin', 0.0), ('m_dLimitPrice', 3.0), ('m_dOrderPriceRMB',
 1.7976931348623157e+308), ('m_dReferenceRate', 0.0), ('m_dShortOccupedMargin', 1.7976931348623157e+308), ('m_dTradeAmount', 0.0), ('m_dTradeAmountRMB
', 1.7976931348623157e+308), ('m_dTradedPrice', 0.0), ('m_eCashgroupProp', 48), ('m_eCoveredFlag', 0), ('m_eEntrustType', 48), ('m_nDirection', 48), (
'm_nErrorID', 2147483647), ('m_nFrontID', -1), ('m_nGroupId', 2147483647), ('m_nHedgeFlag', 49), ('m_nOffsetFlag', 48), ('m_nOpType', 2147483647), ('m
_nOrderPriceType', 50), ('m_nOrderStatus', 54), ('m_nOrderStrategyType', 1395535921), ('m_nOrderSubmitStatus', 51), ('m_nRef', 0), ('m_nSessionID', -1
), ('m_nTaskId', -1), ('m_nVolumeTotal', 100), ('m_nVolumeTotalOriginal', 100), ('m_nVolumeTraded', 0), ('m_strAccountID', '66301009048'), ('m_strAcco
untKey', '2____10166____0001____49____66301009048____'), ('m_strAccountName', ''), ('m_strAccountRemark', ''), ('m_strBrokerName', ''), ('m_strCancelI
nfo', ''), ('m_strCompactNo', ''), ('m_strErrorMsg', ''), ('m_strExchangeID', 'SZ'), ('m_strExchangeName', '深交所'), ('m_strInsertDate', '20220501'),
 ('m_strInsertTime', '144732'), ('m_strInstrumentID', '000725'), ('m_strInstrumentName', '京东方Ａ'), ('m_strLocalInfo', ''), ('m_strOptName', '限价买
入'), ('m_strOption', ''), ('m_strOrderParam', ''), ('m_strOrderRef', '100926249'), ('m_strOrderStrategyType', ''), ('m_strOrderSysID', '318'), ('m_st
rProductID', ''), ('m_strProductName', ''), ('m_strRemark', ''), ('m_strSource', ''), ('m_strUnderCode', ''), ('m_strXTTrade', '其他终端'), ('m_xtTag'
, None)]
"""
def getattr(o,k):
  gg = globals()
  ll = locals()
  return tryx(lambda:eval(f'o.{k}',gg,ll))

def yielder(func,wrap=tryx,do_yield=True): yield (wrap(func) if wrap else func())

def yielder_loop(func,wrap=tryx,do_yield=True):
  while True:
    rt = yield from yielder(func,wrap,do_yield)
    if do_yield and rt is not None: yield rt

import threading
try_async=lambda func:threading.Thread(target=func).start()

#my_eval = eval
my_eval = lambda s,glb=globals():eval(s,glb)

my_encode = str

# 我的账号
def my_acct(acct=None):
    global g_acct
    if acct: g_acct = acct
    if not g_acct: raise Exception('empty g_acct') 
    return g_acct

# 我的持仓 ( TODO )
if __name__ == '__main__':
	def my_pos():
		rt = []
		for v in xt_trader.query_stock_positions(StockAccount(g_acct)):
			code = v.stock_code
			code6 = code[:6]
			mk2 = code[7:9]
			rt.append([code6,mk2,v.volume])
		return rt
else:
	def my_pos(accttype='stock',datatype='position',acct=None):
		rt = []
		debug = 0
		for obj in get_trade_detail_data(acct or my_acct(),accttype,datatype):
			code6 = str(obj.m_strInstrumentID)
			mk2 = str(obj.m_strExchangeID)
			vol = float(obj.m_nVolume)
			cst = float(obj.m_dOpenPrice)
			rt.append([code6,mk2,vol,cst])

			#mkp = float(obj.m_dMarketValue)
			#prz = float(obj.m_dSettlementPrice)
			##last = float(obj.m_nYesterdayVolume) # TODO
			#last = float(obj.m_dLastPrice)
			#rt.append([code6,mk2,vol,mkp,prz,last])
		return rt

# 最近单号。高频不精准
def my_last_order_id(accttype='stock',datatype='order',acct=None):
    return get_last_order_id(acct or my_acct(),accttype,datatype)

# 下单函数
def my_order(code,amt,prz=0,sleep_order_id=0,acct=None):
    global g_ctx
    if not acct: acct = my_acct()
    rst = passorder(23 if amt>0 else 24,1101,acct,code,11 if prz>0 else 14,prz,abs(amt),2,g_ctx)
    print('my_order:',code,amt,prz)
    if sleep_order_id>0:
        sleep(sleep_order_id)
        order_id = my_last_order_id(acct=acct)
        print('.order_id=',order_id)
        return order_id
    print('=>',rst)
    return rst

# 很危险的一键清仓
#my_pos_clear=lambda:[my_order('{}.{}'.format(v[0],v[1]),-round(v[2])) for v in my_pos() if v[2]>0]

# price only, deprecated!
my_price=lambda sector='沪深A股':dict([(v[0],round(v[1]['lastPrice'],3)) for v in g_ctx.get_full_tick(get_stock_list_in_sector(sector)).items()])

#my_price2=lambda sector='沪深A股':dict([(v[0],[round(v[1]['lastPrice'],3),round(v[1]['lastClose'] or 0,3),v[1]['timetag']]) for v in g_ctx.get_full_tick(get_stock_list_in_sector(sector)).items()])

# which having lastClose
my_price2=lambda sector='沪深A股':dict([(v[0],[round(v[1]['lastPrice'],3),round(v[1]['lastClose'] or 0,3)]) for v in g_ctx.get_full_tick(get_stock_list_in_sector(sector)).items()])

# for old v1...(lastClose
my_price_v1=lambda sector='沪深A股':dict([(v[0],[round(v[1]['lastPrice'],3),round(v[1]['lastClose'] or 0,3)]) for v in g_ctx.get_full_tick(['000001.SH','399001.SZ','399006.SZ','399300.SZ','399905.SZ']+get_stock_list_in_sector(sector)).items()])

# 获取sector对应的产品列表
my_prod_a = lambda sector:get_stock_list_in_sector(sector)

list_a_default = []
# 拉价格
#field_a_default = ['lastPrice','lastClose','timetag']
field_a_default = ['lastPrice','lastClose']
#field_a_ext_default = ['lmt','dlt']
field_a_ext_default = ['lmt']
def pull_price(lmt=None,list_a=None,field_a=field_a_default,field_a_ext=field_a_ext_default):
  if list_a is None: list_a = list_a_default
  rt = {}
  if lmt is None: lmt = time_maker(-7,outfmt='')
  for k,v in g_ctx.get_full_tick(list_a).items():
    o = {}
    v_lmt = time_maker(date=v['timetag'][:17],infmt="%Y%m%d %H:%M:%S",outfmt='')
    if v_lmt>lmt:
      for field in field_a:
        o[field] = v[field]
        if 'lmt' in field_a_ext: o['lmt'] = v_lmt
        if 'dlt' in field_a_ext: o['dlt'] = v_lmt - lmt
      rt[k] = o
  return rt

def pull_price_a(lmt=None,list_a=None,field_a=field_a_default,field_a_ext=field_a_ext_default):
  if list_a is None: list_a = list_a_default
  o = pull_price(lmt,list_a,field_a)
  rt_head = ['code','lastPrice','lastClose']
  if 'lmt' in field_a_ext: rt_head.append('lmt')
  if 'dlt' in field_a_ext: rt_head.append('dlt')
  rt = [rt_head]
  for k,v in o.items():
    lastPrice = v['lastPrice']
    lastClose = v['lastClose']
    if lastPrice>0:
      rt_row = [k,lastPrice, lastClose]
      if 'lmt' in field_a_ext: rt_row.append(v['lmt'])
      if 'dlt' in field_a_ext: rt_row.append(v['dlt'])
      rt.append(rt_row)
  return rt

# 暴露接口的处理
def handle_bin(conn):
	s = tryx(lambda:conn.recv(),False)
	if s is None:
		print('handle_bin nothing recv',type(conn))
		return
	rt = tryx(lambda:str(my_eval(s,globals())),str)
	print('handle_bin',s,'=>',rt)
	conn.send(my_encode(rt))

# 启动接口服务器
def start_server():
	global server,server_i
	if server_i>0:
		print('start_server() quick for server is not None')
		return

	from multiprocessing.connection import Listener
	with Listener(address=address) as server:
		server_i+=1
		#print(f'try start_server at {address}',type(server),server_i)
		print(f'try start_server at {address}',server_i)

		while True:
			if server_i==0:
				print('break loop 2')
				break
			#print('round')
			for conn in yielder(server.accept,wrap=lambda func:tryx(func,False)):
				if server_i==0:
					print('break loop')
					break
				if conn is not None:
					#print('type(conn)',type(conn))
					#tryx(lambda:handle_bin(conn))  # sync mode....
					try_async(lambda:handle_bin(conn)) # async mode which faster
	print('start_server ends.')

# 关闭接口服务器 (qmt有bug，基本不需要)
def stop_server():
	global server,server_i
	server_i=0
	if server_i >0:
		print('stop_server')
		server.close()
		#del server
		server = None
	print('stop_server ends.',type(server),server_i)

######################################### QMT 策略调用入口
def init(ContextInfo):
	global g_ctx
	g_ctx = ContextInfo

	#try_async(start_server) # NOTES: qmt failed async start...
	start_server() # sync start...
	print('inited ends')

def stop(ContextInfo):
	try_async(stop_server)
	print('stop done.')

print('__name__=',__name__)
if __name__ == '__main__': # stand alone mode in xt-mini
	class obj(dict):# dict+
		def __init__(self,pa=None):
			for k in pa or {}:self[k]=pa[k]
		def __getitem__(self,key): return self.get(key)
		def __getattr__(self,key): return self[key]
		def __setattr__(self,k,v): self[k]=v

	from time import sleep
	import xtquant
	from xtquant.xttrader import XtQuantTrader, XtQuantTraderCallback
	from xtquant.xttype import StockAccount
	from xtquant.xtdata import get_full_tick, get_instrument_detail, get_stock_list_in_sector 
	from xtquant.xtdata import *
	from xtquant import xtconstant
        work_dir=os.path.dirname(os.path.abspath(__file__))
        print("work_dir",work_dir)

	#xt_trader_path = tryx(lambda:argv[1]) or work_dir
	xt_trader_path = argv[1]
	#xt_trader_sess = tryx(lambda:int(argv[2]),False) or 0
	xt_trader_sess = 0

	xt_trader = XtQuantTrader(xt_trader_path, xt_trader_sess)
	xt_trader.start()
	xt_trader_rt = xt_trader.connect()
	#print('xt_trader_rt',xt_trader_rt)
	assert xt_trader_rt==0, f'failed connect xt_trader {xt_trader_rt} seems need to try again?'

	#a = xt_trader.query_stock_positions(StockAccount(g_acct))
	#print('len(pos_a)=',a,len(a))

	#passorder(23 if amt>0 else 24,1101,acct,code,11 if prz>0 else 14,prz,abs(amt),2,g_ctx)
	#order_stock(acct, stock_code, order_type, volume, price_type, price, strategy_name, tzbz)
	passorder = lambda order_type,tmp1101,acct,code,price_type,price,order_volume,tmp2,tmp_ctx:xt_trader.order_stock( StockAccount(acct),code,order_type, order_volume,price_type,price)

	get_trade_detail_data = lambda acct, acct_type=None, data_type=None:xt_trader.query_stock_positions(StockAccount(acct))
	g_ctx = obj()
	g_ctx.get_full_tick = get_full_tick

	# 一些常用sector(部分在xtquant无效,估计是没有程序自己去维护sector表
	l_hs_300 = my_prod_a('沪深300') # failed in mini-mode...

	l_hs_idx = my_prod_a('沪深指数')
	l_hs_agu = my_prod_a('沪深A股')
	l_hs_kzz = my_prod_a('沪深转债')

	l_hs_etf = my_prod_a('沪深ETF')
	l_hs_zq = my_prod_a('沪深债券')
	l_hs_jj = my_prod_a('沪深基金')

	# 默认 a股、可转债、场内基金、指数
	list_a_default = l_hs_agu+l_hs_kzz+l_hs_etf+l_hs_idx

	init(g_ctx)
	#assert 0==xt_trader_rt, f'XtQuant Conn Failed:{xt_trader_rt}'
	#xt_trader.subscribe(g_acct)

else:
	l_hs_300 = my_prod_a('沪深300') # failed in mini-mode...

	l_hs_idx = my_prod_a('沪深指数')
	l_hs_agu = my_prod_a('沪深A股')
	l_hs_kzz = my_prod_a('沪深转债')

	l_hs_etf = my_prod_a('沪深ETF')
	l_hs_zq = my_prod_a('沪深债券')
	l_hs_jj = my_prod_a('沪深基金')

	# 默认 a股、可转债、场内基金、指数
	list_a_default = l_hs_agu+l_hs_kzz+l_hs_etf+l_hs_idx

# TODO:
#self.t.register_callback(self)
#self.t.start()
#r1 = self.t.connect()
#self.t.subscribe(self.acc)

# query_stock_order
#order = self.t.query_stock_order(self.acc, cancel_error.order_id)

#xt_trader.query_stock_trades(StockAccount('g_acct'))
#xt_trader.query_stock_positions(StockAccount('g_acct'))

# see xtquant.md



